﻿/*
Copyright (C) 2010 INOUE Hiroyuki

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace CodePlex.Dombly.DiskUsage.BusinessLogic.Extensions
{
    /// <summary>
    /// Extends System.IO.FileInfo class.
    /// </summary>
    static partial class FileInfoExtensions
    {
        #region constant
        const uint FORMAT_MESSAGE_ALLOCATE_BUFFER   = 0x0100;
        const uint FORMAT_MESSAGE_ARGUMENT_ARRAY    = 0x2000;
        const uint FORMAT_MESSAGE_FROM_HMODULE      = 0x0800;
        const uint FORMAT_MESSAGE_FROM_STRING       = 0x0400;
        const uint FORMAT_MESSAGE_FROM_SYSTEM       = 0x1000;
        const uint FORMAT_MESSAGE_IGNORE_INSERTS    = 0x0200;
        const uint FORMAT_MESSAGE_MAX_WIDTH_MASK    = 0x00FF;

        const int FORMAT_MESSAGE_BUFFER_SIZE = 255;
        #endregion

        #region wrapper methods for extern functions

        #region GetCompressedFileSize
        /// <summary>
        /// Gets compressed file size of specified file.
        /// </summary>
        /// <param name="fileName">File name to get size.</param>
        /// <returns>Compressed file size in byte.</returns>
        private static long GetCompressedFileSize(string fileName)
        {
            uint lWord, hWord = 0u;
            lWord = NativeMethods.GetCompressedFileSizeW(fileName, ref hWord);
            int error = Marshal.GetLastWin32Error();
            if (lWord == 0xffffffff && error != 0)
            {
                var buf = new StringBuilder(FORMAT_MESSAGE_BUFFER_SIZE);
                NativeMethods.FormatMessageW(
                        FORMAT_MESSAGE_FROM_SYSTEM,
                        IntPtr.Zero, (uint)error, 0u,
                        buf, buf.Capacity, IntPtr.Zero);
                throw new Exception(fileName + ": " + buf.ToString());
            }
            return ((long)hWord << 32) + lWord;
        }
        #endregion

        #region GetBytesPerCluster
        /// <summary>
        /// Gets bytes per cluster in specified volume.
        /// </summary>
        /// <param name="rootPathName">Root path.</param>
        /// <returns>Bytes per cluster.</returns>
        private static long GetBytesPerCluster(string rootPathName)
        {
            uint s_c, b_s, fc, c;
            s_c = b_s = fc = c = 0u;
            NativeMethods.GetDiskFreeSpaceW(rootPathName, ref s_c, ref b_s, ref fc, ref c);
            return (long)s_c * (long)b_s;
        }
        #endregion

        #endregion

        #region extend methods for FileInfo class

        #region GetCompressedSize
        /// <summary>
        /// Gets compressed file size of the entity of specified FileInfo.
        /// </summary>
        /// <param name="fi">FileInfo that represent the target file.</param>
        /// <returns>Compressed file size in byte.</returns>
        public static long GetCompressedSize(this FileInfo fi)
        {
            return GetCompressedFileSize(fi.FullName);
        }
        #endregion

        #region GetBytesPerCluster
        /// <summary>
        /// Gets bytes per cluster in the volume where the entity of 
        /// specified FileInfo is placed.
        /// </summary>
        /// <param name="fi">
        /// FileInfo that represent the target volume.
        /// </param>
        /// <returns>Bytes per cluster.</returns>
        public static long GetBytesPerCluster(this FileInfo fi)
        {
            string rootPath = fi.Directory.Root.FullName;
            if (!_BytesPerCluster.ContainsKey(rootPath))
                _BytesPerCluster[rootPath] = GetBytesPerCluster(rootPath);
            return _BytesPerCluster[rootPath];
        }
        private static Dictionary<string, long> _BytesPerCluster = new Dictionary<string, long>();
        #endregion

        #endregion

        #region subclasses
        static class NativeMethods
        {
            [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
            internal extern static uint GetCompressedFileSizeW(string fileName, ref uint highWord);

            [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
            internal extern static uint GetDiskFreeSpaceW(string rootPathName,
                ref uint sectorsPerClusters,
                ref uint bytesPerSector,
                ref uint freeClusters,
                ref uint clusters);

            [DllImport("kernel32.dll", CharSet=CharSet.Unicode)]
            internal extern static uint FormatMessageW(uint flags,
                IntPtr source, uint messageId, uint languageId,
                StringBuilder buffer, int size, IntPtr arguments);
        }
        #endregion
    }
}
